/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFTOFWall
  \brief TOF Wall class. Just a container for holding hits
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-10-29
  \date 2024-03-16 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-10-29   <td>Asia Sun    <td>file created                  </tr>
  <tr>  <td>2024-03-16   <td>Asia Sun    <td>optimize for the new TOFWall  </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <cstring>
#include <TClonesArray.h>
#include <TGraph.h>
#include <TTree.h>
#include "ETFRun.h"
#include "ETFTOFWall.h"
#include "ETFDetectorManager.h"
#include "ETFPlaHit.h"
#include "ETFHPTDCCh.h"
#include "ETFTrack.h"
#include "ETFParaManager.h"
#include "ETFMath.h"
#include "ETFMsg.h"

using std::cout;
using std::endl;

ETFTOFWall::ETFTOFWall(const char *name, int ns, double width, double length) :
ETFDetector(name), NS(ns), WIDTH(width), LENGTH(length), fSingleEnded(false),
fNFiredStrip(-1), fNFiredStripSingle(-1){
  fStripIdMap = new int[NS];
  fDelayStr = new double[NS];
  // NOTE that the chMap generating rule is upon how specifically the daq is arranged
  const ETFYaml &singleConfig = (*fpm->GetChIdPara())[(string(name)+"_single").c_str()];
  if(singleConfig) fSingleEnded = singleConfig.as<bool>();
  const bool isq = fQChIdConfig.good();
  for(int i = 0; i < NS; i++){
    fStripIdMap[i] = -1;
    if(fChIdConfig[i]){ // j: [0-1-2-3]: [UV-UH-DV-DH]
      const double chid0 = fChIdConfig[i].as<int>();
      if(chid0 >= 0){
        SetChMap(chid0, fDetId + (i<<8)); // UV
        SetChMap(chid0 + 1, fDetId + (i<<8) + (1<<16)); // UH
        if(isq) SetChMap(fQChIdConfig[2*i].as<int>(), fDetId + (i<<8) + (4<<16)); // UQ
        if(!fSingleEnded){
          SetChMap(chid0 + 2, fDetId + (i<<8) + (2<<16)); // DV
          SetChMap(chid0 + 3, fDetId + (i<<8) + (3<<16)); // DH
          if(isq) SetChMap(fQChIdConfig[2*i + 1].as<int>(), fDetId + (i<<8) + (5<<16)); // DQ
        } // end if(issingle)
      } // end if(chid0 >= 0)
    } // end if
    else ETFMsg::Warn("ETFTOFWall", "ctor: chId of strip#%d not found in yaml config file", i);
  } // end for over strips
  const ETFYaml &delayConfig = (*fpm->GetDelayPara())[ETFMsg::Form("%sstr", name)];
  if(delayConfig) for(int i = 0; i < NS; i++) fDelayStr[i] = delayConfig[i].as<double>();
  else for(int i = 0; i < NS; i++) fDelayStr[i] = 0.;

  fHits = new TClonesArray("ETFPlaHit");
  fHits->SetName(name);
  fNStripStrayLB = -0.7;
  fNStripStrayHB = 0.7;
} // end ctor

ETFTOFWall::~ETFTOFWall(){
  delete [] fStripIdMap;
  delete [] fDelayStr;
  if(fHits) delete fHits;
} // end the dtor

void ETFTOFWall::Register(){
  if(fHits) fTree->Branch(fHits->GetName(), &fHits);
  string b = Form("%s_multi", GetName());
  fTree->Branch(b.data(), &fNFiredStrip, (b+"/S").data());
} // end member function Register

// initialize the detector
void ETFTOFWall::Initialize(){
  ETFDetector::Initialize();
  for(int i = fHits->GetEntriesFast(); i--;) fStripIdMap[i] = -1;
  fHits->Clear();
  fNFiredStrip = fNFiredStripSingle = 0;
} // end member function Initialize

// get fired strip by serial id
ETFPlaHit *ETFTOFWall::GetStrip(int id) const{
  if(id >= 0 && id < NS && fStripIdMap[id] >= 0)
    return static_cast<ETFPlaHit *>(fHits->At(fStripIdMap[id]));
  return nullptr;
} // end member function GetStrip

// add the data channel to the detector
void ETFTOFWall::Assign(const ETFRawChannel *ch, unsigned uid){
  const int udvh = uid>>16 & 0x7; // 0-1-2-3-4-5: uv-uh-dv-dh-uq-dq
  // if(1 == udvh || 3 == udvh) return; // ommit (SFE16) TOT channel

  const int stripId = uid>>8 & 0xFF;
  if(stripId >= NS)
    ETFMsg::Error("ETFTOFWall", "Assign: stripId %d out of range, NS=%d", stripId, NS);
  AddHit(ch, stripId, udvh); // if(stripId % 2)
} // end member function Assign

// add strip to fGDumb
void ETFTOFWall::ConstructGraph(){
  if(fGDumb->GetN()) return; // has called

  fGDumb->SetMarkerStyle(28);
  fGDumb->SetMarkerColor(30); // gray
  for(int i = 0; i < NS; i++){
    const double x0 = WIDTH*((1.-NS)/2.+i);
    double p[3] = {x0, 0., 0.}; // cos(fAng[0])*
    TransformToLabRef(p);
    fGDumb->SetPoint(i, p[2], p[0]);
  } // end for over i
} // end member function ConstructGraph

void ETFTOFWall::AddHit(const ETFRawChannel *ch, int stripId, int udvh){
  ETFPlaHit *hit = nullptr;
  for(auto p : *fHits){
    ETFPlaHit *h = static_cast<ETFPlaHit *>(p);
    if(h->GetStripId() == stripId){ // old strip
      hit = h; continue;
    } // end if
  } // end for
  if(!hit && ch->IsT()){ // create a new strip: Q ch does not trigger this
    fStripIdMap[stripId] = fHits->GetEntriesFast();
    hit = static_cast<ETFPlaHit *>(fHits->ConstructedAt(fHits->GetEntriesFast()));
    hit->Initialize();
    hit->SetName(ETFMsg::Form("%s_StripId%d", GetName(), stripId));
    hit->SetStripId(stripId);
    hit->SetLength(LENGTH);
    hit->SetDelay(fDelay + fDelayStr[stripId]);
    // assign strip position
    static const double TMP = (1.-NS)/2.;
    // const double x0 = WIDTH*(TMP + stripId);
    hit->SetX(WIDTH*(TMP + stripId));
    hit->SetY(0.);
    hit->SetZ(0.);

    TransformToLabRef(hit);
    fIsFired = true;
  } // end if
  if(ch->IsT()) hit->SetTChannel(ch, udvh);
  else hit->SetQChannel(ch, udvh);
} // end member function AddHit

bool ETFTOFWall::AssignDriftTimeStart(ETFTrack *trk) const{
  double nstripStrayMin = 1.e200;
  double z, x;
  int id = -1;
  for(auto p : *fHits){
    ETFPlaHit *h = dynamic_cast<ETFPlaHit *>(p);
    if(h->GetFiredStatus() != 4) continue;
    h->GetZX(z, x);
    const double nstripStray = trk->DistanceToLine(z, x);
    if(fabs(nstripStray) < fabs(nstripStrayMin)){
      nstripStrayMin = nstripStray;
      id = h->GetStripId();
    } // end if
  } // end for over TOF Wall strip hits
  ETFPlaHit *hh = GetStrip(id);
  double t0 = hh->GetTime();

#define TOFW_RESELECTION
#ifdef TOFW_RESELECTION
  //-- reselection starts for the new TOF Wall    Mar 16, 2024 --//
  // OK, here we do a second search over the adjacent strips to select the one
  // with the earliest hit
  const int idm = id - 1, idp = id + 1;
  // 1. compare with the left
  ETFPlaHit *hm = GetStrip(idm);
  if(hm && 4 == hm->GetFiredStatus() && hm->GetTime() < t0){
    t0 = hm->GetTime();
    hm->GetZX(z, x);
    nstripStrayMin = trk->DistanceToLine(z, x);
    id = idm;
  } // end if
  // 2. compare with the right
  ETFPlaHit *hp = GetStrip(idp);
  if(hp && 4 == hp->GetFiredStatus() && hp->GetTime() < t0){
    t0 = hp->GetTime();
    hp->GetZX(z, x);
    nstripStrayMin = trk->DistanceToLine(z, x);
    id = idp;
  } // end if
  //-- the selection is over here --//
#endif // TOFW_RESELECTION

  nstripStrayMin /= WIDTH; // in strip width
  // not close enough to any strip
  if(!ETFMath::Within(nstripStrayMin, fNStripStrayLB, fNStripStrayHB) || -1 == id)
    return false;

  trk->SetFiredStripId(id);
  trk->SetNStripStray(nstripStrayMin);
  trk->SetDriftTimeStart(t0);
  return true;
} // end member function AssignDriftTimeStart

void ETFTOFWall::SetNStripStrayBound(double lb, double hb){
  if(lb < hb){ fNStripStrayLB = lb; fNStripStrayHB = hb; }
  else{ fNStripStrayLB = hb; fNStripStrayHB = lb; }
} // end member function SetNStripStrayBound


void ETFTOFWall::DrawHits(){
  for(const auto &p : *fHits) if(p){
    ETFPlaHit *h = dynamic_cast<ETFPlaHit *>(p);
    if(h->GetFiredStatus()) h->Draw();
  } // end for and if
} // end member function DrawHits

void ETFTOFWall::Print(){
  if(!fIsFired) return;
  ETFDetector::Print();
  cout << "Strip Width: " << WIDTH << " mm" << endl;
  cout << "Nof Properly Fired (fired status == 4) Strips: " << GetNFiredStrip() << endl;
  for(auto p : *fHits) static_cast<ETFPlaHit *>(p)->Print();
} // end member function Print

// finish the processing of all the assigned data
void ETFTOFWall::PostAssign(){
  for(TObject *b : *fHits){
    ETFPlaHit *h = static_cast<ETFPlaHit *>(b);
    // h->Filter(fTrigLB, fTrigHB);
    h->Assign();
    const int fired = h->GetFiredStatus();
    if(4 == fired) fNFiredStrip++;
    if(1 == fired) fNFiredStripSingle++;
  } // end if
} // end member function PostAssign
